package selectlist

import (
	"gioui.org/f32"
	"gioui.org/io/key"
	"gioui.org/io/pointer"
	"gioui.org/layout"
	"gioui.org/op"
	"gioui.org/op/clip"
	"gioui.org/unit"
	"gioui.org/widget"
	"gioui.org/widget/material"
	"image"
)

type SelectList struct {
	widget.List
	Selected   int
	Hovered    int
	ItemHeight unit.Dp
	focused    bool
	Theme      *material.Theme
}

func (list *SelectList) Layout(gtx layout.Context, length int, element layout.ListElement) layout.Dimensions {
	return FocusBorder(list.Theme, list.focused).Layout(gtx, func(gtx layout.Context) layout.Dimensions {
		size := gtx.Constraints.Max
		gtx.Constraints = layout.Exact(size)
		defer clip.Rect{Max: size}.Push(gtx.Ops).Pop()

		key.InputOp{
			Tag: list,
			Keys: key.NameUpArrow + "|" + key.NameDownArrow + "|" +
				key.NameHome + "|" + key.NameEnd + "|" +
				key.NamePageUp + "|" + key.NamePageDown,
		}.Add(gtx.Ops)

		pointer.InputOp{
			Tag:          list,
			Types:        pointer.Press | pointer.Move,
			ScrollBounds: image.Rectangle{},
		}.Add(gtx.Ops)

		changed := false
		grabbed := false

		itemHeight := gtx.Metric.Dp(list.ItemHeight)

		pointerClicked := false
		pointerHovered := false
		pointerPosition := f32.Point{}
		for _, ev := range gtx.Events(list) {
			switch ev := ev.(type) {
			case key.Event:
				if ev.State == key.Press {
					offset := 0
					switch ev.Name {
					case key.NameHome:
						offset = -length
					case key.NameEnd:
						offset = length
					case key.NameUpArrow:
						offset = -1
					case key.NameDownArrow:
						offset = 1
					case key.NamePageUp:
						offset = -list.List.Position.Count
					case key.NamePageDown:
						offset = list.List.Position.Count
					}

					if offset != 0 {
						target := list.Selected + offset
						if target < 0 {
							target = 0
						}
						if target >= length {
							target = length - 1
						}
						if list.Selected != target {
							list.Selected = target
							changed = true
						}
					}
				}
			case key.FocusEvent:
				if list.focused != ev.Focus {
					list.focused = ev.Focus
					op.InvalidateOp{}.Add(gtx.Ops)
				}
			case pointer.Event:
				switch ev.Type {
				case pointer.Press:
					if !list.focused && !grabbed {
						grabbed = true
						key.FocusOp{Tag: list}.Add(gtx.Ops)
					}
					// TODO: find the item
					pointerClicked = true
					pointerPosition = ev.Position
				case pointer.Move:
					pointerHovered = true
					pointerPosition = ev.Position
				case pointer.Cancel:
					list.Hovered = -1
				}
			}
		}

		if pointerClicked || pointerHovered {
			clientClickY := list.Position.First*itemHeight + list.Position.Offset + int(pointerPosition.Y)
			target := clientClickY / itemHeight
			if 0 <= target && target <= length {
				if pointerClicked && list.Selected != target {
					list.Selected = target
				}
				if pointerHovered && list.Hovered != target {
					list.Hovered = target
				}
			}
		}

		if changed {
			pos := &list.List.Position
			switch {
			case list.Selected < pos.First+1:
				list.List.Position = layout.Position{First: list.Selected - 1}
			case pos.First+pos.Count-1 <= list.Selected:
				list.List.Position = layout.Position{First: list.Selected - pos.Count + 2}
			}
		}

		return material.List(list.Theme, &list.List).Layout(gtx, length,
			func(gtx layout.Context, index int) layout.Dimensions {
				gtx.Constraints = layout.Exact(image.Point{
					X: gtx.Constraints.Max.X,
					Y: itemHeight,
				})
				return element(gtx, index)
			})
	})
}
